package com.bingxue.edu.framework.config;

import com.bingxue.edu.framework.common.util.SpringContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.BindHandler;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.PropertySourcesPlaceholdersResolver;
import org.springframework.boot.context.properties.bind.handler.IgnoreErrorsBindHandler;
import org.springframework.boot.context.properties.bind.handler.IgnoreTopLevelConverterNotFoundBindHandler;
import org.springframework.boot.context.properties.bind.handler.NoUnboundElementsBindHandler;
import org.springframework.boot.context.properties.source.ConfigurationPropertySources;
import org.springframework.boot.context.properties.source.UnboundElementsSourceFilter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author panbo
 * @since 2023/9/9
 */
@Slf4j
@Order(Ordered.HIGHEST_PRECEDENCE)
@Component
public class DynamicConfigRefresher implements ApplicationContextAware, InitializingBean {

    private static final String SOURCE_NAME = "dynamic-property";

    private ApplicationContext applicationContext;

    private Binder binder;

    private Map<String, Object> oldValues = new HashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void afterPropertiesSet() {
        ConfigurableEnvironment environment = (ConfigurableEnvironment)this.applicationContext.getEnvironment();
        MutablePropertySources propertySources = environment.getPropertySources();
        this.binder = new Binder(ConfigurationPropertySources.from(propertySources),
                new PropertySourcesPlaceholdersResolver(propertySources),
                new DefaultConversionService(),
                ((ConfigurableApplicationContext)this.applicationContext).getBeanFactory()::copyRegisteredEditorsTo);
    }

    public void refresh(Map<String, Object> newValues) {
        List<String> keys = differenceKeys(newValues);
        if (!keys.isEmpty()) {
            ConfigurableEnvironment environment = (ConfigurableEnvironment) SpringContextHolder.getApplicationContext().getEnvironment();
            environment.getPropertySources().addFirst(new MapPropertySource(SOURCE_NAME, newValues));
            this.applicationContext.getBeansWithAnnotation(DynamicConfig.class).forEach((beanName, bean) -> {
                Bindable<?> target = Bindable.ofInstance(bean).withAnnotations(AnnotationUtils.findAnnotation(bean.getClass(), ConfigurationProperties.class));
                ConfigurationProperties annotation = target.getAnnotation(ConfigurationProperties.class);
                if(annotation != null) {
                    BindHandler bindHandler = getBindHandler(target, annotation);
                    this.binder.bind(annotation.prefix(), target, bindHandler);
                }
            });
            oldValues = newValues;
        }
        log.info("配置刷新完成，刷新数量: {}, keys: {}", keys.size(), keys);
    }

    private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperties annotation) {
        BindHandler handler = new IgnoreTopLevelConverterNotFoundBindHandler();
        if (annotation.ignoreInvalidFields()) {
            handler = new IgnoreErrorsBindHandler(handler);
        }
        if (!annotation.ignoreUnknownFields()) {
            UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
            handler = new NoUnboundElementsBindHandler(handler, filter);
        }
        return handler;
    }

    /**
     * 对比两个map是否有差异
     * @param newValues 新的配置值
     * @return 是否存在差异
     */
    private List<String> differenceKeys(Map<String, Object> newValues) {
        List<String> keys = new ArrayList<>();
        for (Map.Entry<String, Object> entry : newValues.entrySet()) {
            if (!entry.getValue().equals(oldValues.get(entry.getKey()))) {
                keys.add(entry.getKey());
            }
        }
        return keys;
    }
}
